《Android 应用 之路》 简易手电筒

前言

快一个月没有写自己的博客了,由于最近换了工作,换了居住地,所以有一些杂事需要处理,从今天开始恢复正常,不赘述了。进入今天的主题 —– 简易的手电筒。

这个Demo中使用的是比较新的API,M版本之后添加的针对于手电筒的接口。这里使用的是Camera的API2接口,主要使用CameraManager中针对于闪光灯的一些方法,对于Camera API2的接口,后面在涉及相机应用的时候,API1和API2应该都会梳理一下,到时候再仔细的研究一下。

思路

实现一个简单的手电筒,考虑到M版本上新增的接口,可以直接通过setTorchMode来改变闪光灯的状态,实现开关,然后根据当前的闪光灯状态,有回调函数,若其他的应用打开了闪光灯或者是关闭了闪光灯,该应用要作出对应的调整,同时,开启和关闭的过程,需要有明显的用户感知和提示,这就要结合NotificationManager和CameraManager的接口一起实现了。

接口介绍

CameraManager.java(frameworks/base/core/java/android/hardware/camera2)
这里写图片描述

方法 含义
TorchCallback 针对闪光灯的回调
AvailabilityCallback 针对相机是否可用的回调
CameraManager() 构造函数
getCameraIdList() 获取相机的Id
registerAvailabilityCallback() 注册相机是否可用的回调
unregisterAvailabilityCallback() 解除注册
registerTorchCallback() 注册针对闪光灯状态的回调
unregisterTorchCallback() 解除注册
getCameraCharacteristics() 传入参数为相机的id,获取相的一参数信息,如支持的预览大小,支持的滤镜等等
openCamera() 传入的参数为相机的id和状态回StateCallback,这个是在CameraDevice中定义的,打开相机操作
setTorchMode() 设置闪光灯的状态

实战代码

1.布局文件

由于是手电筒,布局文件很简单,主布局中只有一个button
activity_custom_button.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/flash_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context=".FlashActivity">

<Button
android:id="@+id/bt_flash"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="@drawable/flash_open" />

</FrameLayout>

显示当前闪光灯被占用的自定义Toast布局
busy_toast.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/busy_snackbar_bg"
android:gravity="center"
android:padding="@dimen/activity_horizontal_margin"
android:text="FlashLight is Busy , Sorry!"
android:textStyle="bold" />

</LinearLayout>

2.代码文件

主要就是两个类,一个是主Activity,一个就是用来执行notification的pendingintent的广播接收器
FlashActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package mraz.com.custombutton;

import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.NotificationCompat;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;

@TargetApi(Build.VERSION_CODES.M)
public class FlashActivity extends AppCompatActivity {

public static final String CLOSE_FLASH_ACTION = "android.intent.action.close_flash";
private static final int NOTIFICATIONID = 0;
private Button btFlash;
private boolean mIsFlashOn = false;
private CameraManager cameraManager = null;
private String[] mCameraIds;
private Notification mFlashOnNotification = null;
private NotificationManager notificationManager = null;
private boolean isFlashAvailbale = true;
private FrameLayout mContentPanel = null;


//闪光灯状态变化的回调
private CameraManager.TorchCallback torchCallback = new CameraManager.TorchCallback() {
@Override
public void onTorchModeUnavailable(String cameraId) {
super.onTorchModeUnavailable(cameraId);
//onTorchModeUnavailable 当前闪光灯不可用,如果当前闪光处于打开状态,则关闭它,并且对应的标志位
if (cameraId.equals(mCameraIds[0]) && mIsFlashOn) {
reverseFlashState();
}
isFlashAvailbale = false;
System.out.println("cameraId = " + cameraId + " onTorchModeUnavailable");
}

@Override
public void onTorchModeChanged(String cameraId, boolean enabled) {
super.onTorchModeChanged(cameraId, enabled);
//onTorchModeChanged 闪光灯状态变化回调 enabled=false 闪光灯关闭
//enabled=true 闪光灯已经开启
//通过这个回调设置标志位,如果当前闪光灯开着但是收到了闪光灯已经被关闭的回调,则改变对应的状态
isFlashAvailbale = true;
System.out.println("cameraid = " + cameraId + " enabled = " + enabled + " misFlashOn = " + mIsFlashOn);
if (cameraId.equals(mCameraIds[0]) && enabled == false && mIsFlashOn) {
reverseFlashState();
}
System.out.println("cameraId = " + cameraId + " onTorchModeChanged enabled = " + enabled);
}
};


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_button);

//闪光灯开关按钮
btFlash = (Button) findViewById(R.id.bt_flash);
//整个布局
mContentPanel = (FrameLayout) findViewById(R.id.flash_content);

btFlash.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
reverseFlashState();
}
});
//根据当前闪光灯装填设置一下UI界面的颜色
changeFlashUi(mIsFlashOn);
}

//flash状态翻转
private void reverseFlashState() {
//如果当前Flash 处于unavailable状态,说明当前闪光灯被占用,无法使用
if (!isFlashAvailbale) {
//显示当前闪光灯被占用的提示
showFlashBusy();
return;
}
changeFlashState(mIsFlashOn);//开->关 关->开
mIsFlashOn = !mIsFlashOn;//标志位装换
changeFlashUi(mIsFlashOn);//界面UI切换,这里主要就是为了突出闪光灯开关的状态不同
applyNotification(mIsFlashOn);//闪光灯开启的提示显示和消除
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void applyNotification(boolean isFlashOn) {
if (!isFlashOn) {
dismissNotification();
return;
}
if (mFlashOnNotification != null && notificationManager != null) {
notificationManager.notify(NOTIFICATIONID, mFlashOnNotification);
}
}

@Override
protected void onResume() {
super.onResume();
cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
cameraManager.registerTorchCallback(torchCallback, null);//注册回调
getCameraList();//获取当前手机的摄像头个数
generateNotify();//生成需要显示的提示,方便后面显示
}

@Override
protected void onDestroy() {
super.onDestroy();
cameraManager.unregisterTorchCallback(torchCallback);//ondestory的时候解除回调
}

@TargetApi(Build.VERSION_CODES.M) //只有M版本的手机可以使用这个方法
private void changeFlashState(boolean isFlashOn) {
if (cameraManager != null && mCameraIds != null) {
try {
cameraManager.setTorchMode(mCameraIds[0], !isFlashOn);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP) //只有L版本的收集可以使用这个方法
private void getCameraList() {
if (cameraManager != null) {
try {
mCameraIds = cameraManager.getCameraIdList();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}

//按钮的背景图切换
private void changeFlashUi(boolean isFlashOn) {
if (isFlashOn) {
btFlash.setBackgroundResource(R.drawable.flash_open);
} else {
btFlash.setBackgroundResource(R.drawable.flash_close);
}
}

//生成notification的大图标
private Bitmap createNotificationLargeIcon(Context c) {
Resources res = c.getResources();
int width = (int) res.getDimension(android.R.dimen.notification_large_icon_width);
int height = (int) res.getDimension(android.R.dimen.notification_large_icon_height);
Bitmap result = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(res, R.mipmap.ic_flash_on_normal), width, height, false);
return result;
}

//生成notification
private void generateNotify() {
if (mFlashOnNotification != null) return;
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setLargeIcon(createNotificationLargeIcon(this))
.setContentTitle("手电筒已开启")
.setContentText("点击可关闭手电筒")
.setSmallIcon(R.mipmap.ic_flash_off_normal, 3)
.setContentIntent(createCloseFlashPendingIntent());
mFlashOnNotification = builder.build();
}

//消除notification
private void dismissNotification() {
if (notificationManager != null && mFlashOnNotification != null) {
notificationManager.cancel(NOTIFICATIONID);
}
}

//创建点击notification对应的PendingIntent
private PendingIntent createCloseFlashPendingIntent() {
Intent intent = new Intent();
intent.setClass(this, FlashCloseReceiver.class);
intent.setAction(CLOSE_FLASH_ACTION);

return PendingIntent.getBroadcast(this, 0, intent, 0);
}

//显示一个手电筒忙碌的提示
private void showFlashBusy() {
View toastContent = getLayoutInflater().inflate(R.layout.busy_toast, null, false);
Toast toast = new Toast(this);
toast.setView(toastContent);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
toast.show();
}
}

FlashCloseReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package mraz.com.custombutton;

import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.os.Build;

public class FlashCloseReceiver extends BroadcastReceiver {
CameraManager mCameraManager = null;
String[] mCameraIds = null;

public FlashCloseReceiver() {
}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("onReceiver");
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
if (mCameraManager != null) {
try {
mCameraIds = mCameraManager.getCameraIdList();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
String action = intent.getAction();
if (action.equals(FlashActivity.CLOSE_FLASH_ACTION)) {
if (mCameraManager != null && mCameraIds != null && mCameraIds.length != 0) {
try {
System.out.println("setTorchMode");
mCameraManager.setTorchMode(mCameraIds[0], false);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
}

实际效果图

手电筒关闭状态

这里写图片描述

手电筒开启状态

这里写图片描述

手电筒开启状态提示信息

这里写图片描述

备注

由于开发时间比较短,测试可能不充分,有问题欢迎留言讨论~

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×